package vooga.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;


/**
 * This class provides a variety of convenience methods for dynamically
 * calling methods and allocating classes.  This utility class simplifies
 * some of Java's reflection API and fixes some issues.
 */
public class Reflection
{
    /**
     * Given a String representing the fully qualified name of a 
     * class, returns an initialized instance of the corresponding 
     * class using default constructor.  Returns null if string 
     * does not name a valid class.
     */
    public static Object createInstance (String className)
        throws ReflectionException
    {
        try
        {
            return Class.forName(className).newInstance();
        }
        catch (ClassNotFoundException e)
        {
            throw new ReflectionException("Badly named class " + className);
        }
        catch (Exception e)
        {
            throw new ReflectionException("No public default constructor for " + className);
        }
    }


    /**
     * Given String representing fully qualified name of a class and 
     * array of actual parameters, returns initialized instance of 
     * the corresponding class using matching constructor.  
     */
    public static Object createInstance (String name, Object[] args)
        throws ReflectionException
    {
        try
        {
            Class c = Class.forName(name);
            Constructor[] ctors = c.getDeclaredConstructors();
            for (int k = 0; k < ctors.length; k++)
            {
                Class[] formals = ctors[k].getParameterTypes();
                if (typesMatch(formals, args))
                {
                    return ctors[k].newInstance(args);
                }
            }
            throw new ReflectionException("No matching public constructor for " + name);
        }
        catch (ClassNotFoundException e)
        {
            throw new ReflectionException("Badly named class " + name);
        }
        catch (Exception e)
        {
            throw new ReflectionException("No matching public constructor for " + name);
        }
    }


    /**
     * Given a target object with a no argument method with the given 
     * name, call the named method on that object and return the result.
     * If the method's return type is void, null in returned.
     */
    public static Object callMethod (Object target, String name)
        throws ReflectionException
    {
        try
        {
            Method toCall = target.getClass().getMethod(name, new Class[0]);
            return toCall.invoke(target, new Object[0]);
        }
        catch (Exception e)
        {
            throw new ReflectionException("No matching public method for " + 
                                          target.getClass().getName());
        }
    }


    /**
     * Given a target object with a method with the given name that takes 
     * the array of given actual parameters, call the named method on that 
     * object and return the result. If the method's return type is void,
     * null in returned.
     */
    public static Object callMethod (Object target, String name, Object[] args)
        throws ReflectionException
    {
        try
        {
            Method[] methods = target.getClass().getMethods();
            for (int k = 0; k < methods.length; k++)
            {
                Class[] formals = methods[k].getParameterTypes();
                if (name.equals(methods[k].getName()) && typesMatch(formals, args))
                {
                    return methods[k].invoke(target, args);
                }
            }
            throw new ReflectionException("No matching public constructor for " + name);
        }
        catch (Exception e)
        {
            throw new ReflectionException("No matching public constructor for " + name);
        }
    }


    /**
     * Given a target object with an instance variable with the given name,
     * get the value of the named variable on that object and return it.
     */
    public static Object getFieldValue (Object target, String name)
        throws ReflectionException
    {
        try
        {
            return target.getClass().getDeclaredField(name).get(target);
        }
        catch (Exception e)
        {
            throw new ReflectionException("No matching public instance variable for " + 
                                          target.getClass().getName());
        }
    }


    // are parameters compatible types and in same order?
    private static boolean typesMatch (Class[] formals, Object[] actuals)
    {
        final String TYPE = "TYPE";

        if (actuals.length == formals.length)
        {
            try
            {
                // check each parameter individually
                for (int k = 0; k < formals.length; k++)
                {
                    // handle primitives specially (Java 1.4 does not do this :()
                    if (formals[k].isPrimitive())
                    {
                        Class thePrimitive = (Class)getFieldValue(actuals[k], TYPE);
                        if (! formals[k].isAssignableFrom(thePrimitive))
                        {
                            // primitives are not exactly the same (but could be double and int)
                            return false;
                        }
                    }
                    else if (! formals[k].isInstance(actuals[k]))
                    {
                        // not an instance of class or its sub-classes
                        return false;
                    }
                }
                return true;
            }
            catch (Exception e)
            {
                // tried to compare primitive to non-primitive
                return false;
            }
        }
        // sizes don't match
        return false;
    }
}
